package ca.scotthyndman.web.swf.common;

import java.util.HashMap;
import java.util.Map;

/**
 * Represents an ActionScript package.
 *
 * @author Scott Hyndman
 */
public class Package extends Class {

	Map<String, Package> childPackages;

	Map<String, Class> childClasses;

	boolean packChildren = false;

	/**
	 * Creates a new root package.
	 */
	public Package() {
		this("");
	}

	/**
	 * Constructs a new package.
	 * 
	 * @param name
	 */
	public Package(String name) {
		super(name);
		childPackages = new HashMap<String, Package>();
		childClasses = new HashMap<String, Class>();
	}

	/**
	 * Returns whether children are packed.
	 * 
	 * @return
	 */
	public boolean areChildrenPacked() {
		return packChildren;
	}

	/**
	 * Sets whether this package's children should be packed.
	 * 
	 * @param packChildren
	 *            <code>true</code> if the child packages and classes of this
	 *            package should be packed.
	 */
	public void setPackChildren(boolean packChildren) {
		this.packChildren = packChildren;
	}

	/**
	 * Gets the fully qualified package name.
	 * 
	 * @return
	 */
	public String getFullName() {
		if (parent == null) {
			return "";
		} else if (parent.parent == null) {
			return name;
		} else {
			return parent.getFullName() + "." + name;
		}
	}

	/**
	 * Gets this package's child classes.
	 * 
	 * @return
	 */
	public Map<String, Class> getChildClasses() {
		return childClasses;
	}

	/**
	 * Gets this package's child packages.
	 * 
	 * @return
	 */
	public Map<String, Package> getChildPackages() {
		return childPackages;
	}

	/**
	 * Adds a child class
	 * 
	 * @param name
	 * @return
	 */
	public Class addChildClass(String name) {
		return addChildClass(new Class(name));
	}

	/**
	 * Adds a child class
	 * 
	 * @param cls
	 * @return
	 */
	public Class addChildClass(Class cls) {
		cls.parent = this;
		childClasses.put(cls.name, cls);
		return cls;
	}

	/**
	 * Adds a named child package.
	 * 
	 * @param name
	 * @return
	 */
	public Package addChildPackage(String name) {
		return addChildPackage(new Package(name));
	}

	/**
	 * Adds a child package.
	 * 
	 * @param pkg
	 */
	public Package addChildPackage(Package pkg) {
		if (childPackages.containsKey(pkg.name)) {
			childPackages.get(pkg.name).merge(pkg);
			return childPackages.get(pkg.name);
		}

		pkg.parent = this;
		childPackages.put(pkg.name, pkg);
		return pkg;
	}

	/**
	 * Merges all of another package's children into this one.
	 * 
	 * @param pkg
	 */
	public void merge(Package pkg) {
		for (Package c : pkg.childPackages.values()) {
			addChildPackage(c);
		}
		for (Class c : pkg.childClasses.values()) {
			addChildClass(c);
		}
	}

	/**
	 * Returns the package named <code>name</code>.
	 * 
	 * @param name
	 *            The name of the package to return, relative to this package.
	 * @return The package named name, or <code>null</code>.
	 */
	public Class getPackageNamed(String name) {
		String[] path = name.split("\\.");
		if (path.length == 1 && path[0].equals(this.name)) {
			return this;
		}

		//
		// Get the child name
		//
		String childName = path[0];
		if (!childPackages.containsKey(childName)) {
			if (childClasses.containsKey(childName)) {
				return childClasses.get(childName);
			}
			return null;
		}

		//
		// Get the path for the child to follow
		//
		String childPath;
		int firstDot = name.indexOf('.');
		if (firstDot == -1) {
			childPath = name;
		} else {
			childPath = name.substring(firstDot + 1);
		}

		return childPackages.get(childName).getPackageNamed(childPath);
	}

}
